Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

fix(orm): properly handle literal array values in policy evaluation for postgres#628

Merged
ymc9 merged 5 commits into
devfrom
fix/issue-595
Jan 28, 2026
Merged

fix(orm): properly handle literal array values in policy evaluation for postgres#628
ymc9 merged 5 commits into
devfrom
fix/issue-595

Conversation

@ymc9
Copy link
Copy Markdown
Member

@ymc9 ymc9 commented Jan 28, 2026

fixes #595

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced array field support with explicit type tracking for improved validation and consistency across database operations.
  • Bug Fixes

    • Improved array containment operations (has, hasEvery, hasSome) with better cross-database compatibility and reliability.
    • Refined array handling with enhanced type safety and validation across all supported database engines.
  • Tests

    • Added regression test for authorization policy enforcement on user operations.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings January 28, 2026 09:29
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jan 28, 2026

Claude finished @ymc9's task —— View job


Code Review Complete ✅

I'll review this pull request that fixes issue #595 regarding PostgreSQL literal array value handling in policy evaluation.

Review Todo List:

  • Read the regression test to understand the specific issue being fixed
  • Review core changes in the policy expression evaluation logic
  • Analyze dialect-specific implementations (PostgreSQL, MySQL, SQLite)
  • Check ORM client changes and name mapping logic
  • Evaluate code quality, potential bugs, and security concerns
  • Assess performance implications
  • Review test coverage adequacy
  • Provide comprehensive feedback and recommendations

Code Review Feedback

Understanding the Issue

The issue was that literal array values in policy expressions (like hasSome(auth().roles, [admin, editor])) were not properly handled for PostgreSQL. The transformer was creating ValueListNode instead of using dialect-specific array value generation, causing PostgreSQL array syntax errors.

✅ Positive Aspects

  1. Clear Problem Identification: The regression test clearly demonstrates the issue with a real-world scenario involving enum arrays in auth policies.

  2. Proper Abstraction: The fix correctly delegates array value creation to dialect-specific implementations via buildArrayValue().

  3. Comprehensive Dialect Support: All three dialects are properly updated:

    • PostgreSQL: Uses ARRAY[...] syntax (supports arrays)
    • MySQL/SQLite: Properly throw errors since they don't support native arrays
  4. Type Safety: Added proper return type annotation to transformValue() method.

🔍 Code Quality Issues

  1. packages/plugins/policy/src/expression-transformer.ts:184-186 - Good change to use dialect-specific array building, but consider caching the expression builder instance.

  2. packages/plugins/policy/src/expression-transformer.ts:558-562 - The nested transformValue call for array elements should preserve element types rather than passing the parent array type.

🐛 Potential Bug

In transformValue() at lines 558-562:

return this.dialect
    .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, type))))
    .toOperationNode();

Issue: Passing the array's type to nested elements is incorrect. For enum arrays like Role[], each element should be typed as Role, not Role[].

Fix: Consider element type detection:

// Get element type for arrays
const elementType = type.endsWith('[]') ? type.slice(0, -2) : type;
return this.dialect
    .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, elementType))))
    .toOperationNode();

⚡ Performance Considerations

  1. Expression Builder Instance: Creating new ExpressionWrapper() for each array element could be optimized.

  2. Recursive Type Checking: The QueryUtils.isEnum() check in transformValue() is called for every value - consider memoization for frequently used types.

🔒 Security Assessment

  • No security concerns identified
  • Proper input validation and type checking maintained
  • SQL injection prevention through parameterized queries preserved

🧪 Test Coverage

  • Excellent: Comprehensive regression test covering the specific issue
  • Good: Tests include enum array handling with @map attribute
  • Coverage: Multiple auth scenarios (admin, editor, viewer roles)

📋 Minor Improvements

  1. packages/orm/src/client/crud/dialects/postgresql.ts: The removal of enum array casting logic appears correct, but ensure this doesn't break other PostgreSQL enum array scenarios.

  2. Type Definitions: Consider adding JSDoc to buildArrayValue() method explaining when it should throw vs. when it should work.

🎯 Recommendations

  1. Fix the type propagation bug in transformValue() for array elements
  2. Add integration tests for nested enum arrays to ensure the fix works comprehensively
  3. Consider performance optimization for expression wrapper creation
  4. Document the dialect differences in array handling for future maintainers

Overall Assessment: ✅ APPROVE with Minor Fixes

This is a solid fix that properly addresses the PostgreSQL array handling issue. The core architecture change (delegating to dialect-specific array building) is correct and well-implemented. The main concern is the type propagation bug for array elements, which should be addressed before merging.

The test coverage is excellent and the changes maintain backward compatibility. Once the type issue is fixed, this PR will be ready to merge.


@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

This pull request refactors array handling across the ORM and schema systems to be dialect-aware and type-explicit. It replaces generic array builders with dialect-specific implementations, introduces explicit type parameters for array expressions, and updates three array-related functions (has, hasEvery, hasSome) to use dialect-specific helpers instead of direct expression builder operations.

Changes

Cohort / File(s) Summary
Core Dialect Array API
packages/orm/src/client/crud/dialects/base-dialect.ts
Refactored abstract array methods: replaced buildArrayLiteralSQL(unknown[]) with buildArrayValue(Expression<unknown>[], elemType: string). Added three new abstract methods for array operations: buildArrayContains, buildArrayHasEvery, buildArrayHasSome. Enhanced buildArrayFilter with validation, receiver logic for enums, and new helper methods.
Database Dialect Implementations
packages/orm/src/client/crud/dialects/mysql.ts, postgresql.ts, sqlite.ts
Implemented new array API across three dialects. MySQL and SQLite throw NotSupportedError for array operations. PostgreSQL implements using @> (containment), && (overlap) operators and custom casting. All removed getFieldSqlType override and updated array construction.
Array Functions
packages/orm/src/client/functions.ts
Updated has, hasEvery, hasSome to accept context parameter and delegate to dialect-specific builders instead of using hardcoded operators. Functions now context-aware for dialect polymorphism.
Expression & Schema Infrastructure
packages/schema/src/expression.ts, expression-utils.ts
Added type: string field to ArrayExpression type. Updated ExpressionUtils.array() signature to require type parameter before items array.
Code Generation & Transformation
packages/sdk/src/ts-schema-generator.ts, packages/plugins/policy/src/expression-transformer.ts
Updated array expression generation to resolve and pass element type. Enhanced expression transformer with ExpressionWrapper handling and dialect-aware array construction via buildArrayValue.
Supporting Infrastructure
packages/orm/src/client/executor/name-mapper.ts, policy-handler.ts, helpers/schema-db-pusher.ts
Name mapper threaded with per-query context and enum type mapping. Policy handler updated to pass field type to buildArrayValue. Schema DB pusher enhanced to resolve enum names via @@Map attributes.
Generated Schema Files
samples/*/zenstack/schema.ts, tests/e2e/*/schema.ts, tests/runtimes/*/schemas/schema.ts, etc.
Systematic update across ~40 generated files: all ExpressionUtils.array([...]) calls now include explicit element type as first parameter (e.g., ExpressionUtils.array("String", [...])). Types inferred from relation fields, indices, and model structures.
Test Coverage
tests/regression/test/issue-595.test.ts
New comprehensive regression test for array functions in access policies, validating policy enforcement across auth contexts with array-based authorization rules.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

The refactoring spans multiple interconnected systems (dialects, expressions, code generation) with logic density in array handling and dialect polymorphism. However, ~40 generated schema files follow a homogeneous pattern (adding type parameters), reducing per-file review burden. Core implementation files require careful scrutiny for SQL generation correctness and type mapping logic.

Poem

🐰 Arrays now speak in dialect's tongue,
With types declared where queries run,
PostgreSQL blooms with operators true,
While MySQL admits what it can't do.
From schema to function, the type-aware dance—
A refactor complete, no malformed chance! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main fix: properly handling literal array values in policy evaluation for PostgreSQL.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding requirements from issue #595: array handling in dialect API [base-dialect, mysql, postgresql, sqlite], policy argument transformation [policy-handler, expression-transformer], and array expression infrastructure [expression.ts, expression-utils.ts].
Out of Scope Changes check ✅ Passed All changes are directly related to fixing array handling in policy evaluation. Schema file updates are necessary follow-ups to the API changes in expression-utils.ts. The regression test validates the fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes issue #595 where array-related functions (has, hasSome) in access policies fail on PostgreSQL. The root cause was that enum values and array expressions were being improperly converted to SQL, resulting in malformed array literals and unsupported expression errors.

Changes:

  • Introduced a new EnumExpression type to properly represent enum values in the schema expression system
  • Replaced direct PostgreSQL array operator usage with dialect-specific methods that correctly construct array values
  • Added proper handling for enum type and value name mapping in the query name mapper

Reviewed changes

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/schema/src/expression.ts Added new EnumExpression type to represent enum values
packages/schema/src/expression-utils.ts Added enum() helper function and isEnum() type guard
packages/sdk/src/ts-schema-generator.ts Changed to generate enum expressions instead of string literals for enum field references
packages/plugins/policy/src/expression-transformer.ts Added handlers for enum and array expressions, simplified call argument transformation
packages/plugins/policy/src/policy-handler.ts Updated to use new buildArrayValue method
packages/plugins/policy/src/expression-evaluator.ts Added evaluation support for enum expressions
packages/orm/src/client/functions.ts Refactored has, hasEvery, hasSome to use dialect methods
packages/orm/src/client/crud/dialects/*.ts Implemented array operation methods for each dialect
packages/orm/src/client/executor/name-mapper.ts Added enum type and value mapping support for CAST nodes
packages/orm/src/client/helpers/schema-db-pusher.ts Updated to use mapped enum names
tests/regression/test/issue-595.test.ts Comprehensive regression test covering the reported issue
Multiple schema files Regenerated to use ExpressionUtils.enum()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/plugins/policy/src/expression-transformer.ts Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jan 28, 2026

Claude finished @ymc9's task —— View job


Claude Code Review Complete


Review Todo List:

  • Understand the issue by reading the regression test
  • Review core changes in expression handling and transformer logic
  • Analyze dialect-specific implementations (PostgreSQL, MySQL, SQLite)
  • Check ORM client changes and name mapping logic
  • Evaluate code quality, potential bugs, and security concerns
  • Assess performance implications
  • Review test coverage adequacy
  • Provide comprehensive feedback and recommendations

Code Review Feedback

✅ Issue Understanding

This PR successfully fixes PostgreSQL array literal handling in policy evaluation. The issue was that enum arrays in expressions like hasSome(auth().roles, [admin, editor]) were creating malformed SQL due to improper array value generation for PostgreSQL's ARRAY[...] syntax.

✅ Architecture & Design Quality

Excellent Design Decisions:

  1. New EnumExpression Type - Clean separation of enum values from string literals in the AST
  2. Dialect Abstraction - Proper delegation to buildArrayValue() method allows database-specific implementations
  3. Comprehensive Mapping - Enhanced name mapping for enum types and values in CAST operations
  4. Expression System Consistency - All expression types handled uniformly through the visitor pattern

🔍 Code Quality Assessment

Strengths:

  • Clear Separation of Concerns: Each dialect properly implements its array capabilities
  • Type Safety: New enum expression type provides better type checking
  • Consistent Error Handling: MySQL/SQLite properly throw errors for unsupported arrays
  • Comprehensive Mapping: Full enum type and value name mapping support

Minor Issues Identified:

  1. packages/plugins/policy/src/expression-transformer.ts:560 - Type Propagation Bug

    // Current (incorrect):
    return this.dialect
        .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, type))))
        .toOperationNode();
    
    // Should be (fix element type):
    const elementType = type.endsWith('[]') ? type.slice(0, -2) : type;
    return this.dialect
        .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, elementType))))
        .toOperationNode();

    Issue: Passing array type to elements instead of element type (e.g., Role[] instead of Role)

  2. packages/plugins/policy/src/expression-transformer.ts:632-656 - Dead Code
    Large block of commented code should be removed to improve maintainability.

⚡ Performance Implications

Positive:

  • Dialect-specific implementations avoid unnecessary abstraction overhead
  • Direct SQL generation for PostgreSQL arrays is efficient

Minor Concerns:

  • Creating ExpressionWrapper instances for each array element could be optimized
  • QueryUtils.isEnum() calls for every value - consider memoization for frequently used types

🔒 Security Assessment

No security concerns identified

  • Proper parameterization maintained
  • No SQL injection vectors introduced
  • Input validation preserved through existing mechanisms

🧪 Test Coverage

Excellent test coverage

  • Comprehensive regression test covering the exact reported issue
  • Tests include enum arrays with @map attributes
  • Multiple auth role scenarios (admin, editor, viewer)
  • Real-world policy expressions tested

💡 Recommendations

Must Fix:

  1. Fix type propagation bug in transformValue() method for array elements

Should Consider:

  1. Remove commented code in expression transformer
  2. Add JSDoc to buildArrayValue() explaining dialect differences
  3. Performance optimization for expression wrapper creation in arrays

Nice to Have:

  1. Integration tests for nested enum arrays
  2. Documentation of dialect array support differences

📋 Dialect Implementation Review

  • PostgreSQL: ✅ Proper ARRAY[...] syntax implementation
  • MySQL: ✅ Correctly throws error (no native array support)
  • SQLite: ✅ Correctly throws error (no native array support)

🎯 Overall Assessment: ✅ APPROVE with Minor Fix

This is a solid, well-architected fix that properly addresses the PostgreSQL array handling issue. The core design of delegating to dialect-specific array building is excellent and maintains the abstraction boundaries properly.

The main concern is the type propagation bug for array elements, which should be addressed before merging. Once fixed, this PR significantly improves PostgreSQL enum array handling while maintaining compatibility across all supported databases.

Impact: This fix enables proper policy evaluation with enum arrays on PostgreSQL, resolving a critical functional gap in the ORM's authorization system.


Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/plugins/policy/src/expression-transformer.ts (1)

165-186: Remove @ts-expect-error from production handlers.

These handler signatures can be typed explicitly to avoid @ts-expect-error. This keeps the policy transformer type-safe and aligned with repo standards.

♻️ Suggested refactor
-import {
-    ExpressionUtils,
-    type ArrayExpression,
-    type CallExpression,
-    type Expression,
-    type FieldExpression,
-    type SchemaDef,
-} from '@zenstackhq/orm/schema';
+import {
+    ExpressionUtils,
+    type ArrayExpression,
+    type CallExpression,
+    type EnumExpression,
+    type Expression,
+    type FieldExpression,
+    type SchemaDef,
+} from '@zenstackhq/orm/schema';

 `@expr`('literal')
-// `@ts-expect-error`
-private _literal(expr: LiteralExpression) {
+private _literal(expr: LiteralExpression, _context: ExpressionTransformerContext): OperationNode {
     return this.transformValue(
         expr.value,
         typeof expr.value === 'string' ? 'String' : typeof expr.value === 'boolean' ? 'Boolean' : 'Int',
     );
 }

 `@expr`('enum')
-// `@ts-expect-error`
-private _enum(expr: { type: string; value: string }) {
+private _enum(expr: EnumExpression, _context: ExpressionTransformerContext): OperationNode {
     return this.dialect.castEnum(this.eb.val(expr.value), expr.type).toOperationNode();
 }

 `@expr`('array')
-// `@ts-expect-error`
-private _array(expr: ArrayExpression, context: ExpressionTransformerContext) {
+private _array(expr: ArrayExpression, context: ExpressionTransformerContext): OperationNode {
     return this.dialect
         .buildArrayValue(expr.items.map((item) => new ExpressionWrapper(this.transform(item, context))))
         .toOperationNode();
 }

Based on learnings: Do not use ts-expect-error in production code within the zenstackhq/zenstack-v3 repo (e.g., packages/*). Use explicit type annotations, targeted type assertions, or refactor to resolve the type error. ts-expect-error may be acceptable only in test files for stubbing or temporary silencing. Ensure production code is type-safe and maintainable.

packages/orm/src/client/executor/name-mapper.ts (1)

263-300: Propagate queryId in the non-table update branch.
The early return drops per-query context when innerTable isn’t a TableNode.

🔧 Proposed fix
-        if (!innerTable || !TableNode.is(innerTable)) {
-            return super.transformUpdateQuery(node);
-        }
+        if (!innerTable || !TableNode.is(innerTable)) {
+            return super.transformUpdateQuery(node, queryId);
+        }
🤖 Fix all issues with AI agents
In `@packages/orm/src/client/crud/dialects/mysql.ts`:
- Around line 231-233: Replace the plain Error thrown in buildArrayValue with
the standardized helper by throwing createNotSupportedError instead; locate the
override buildArrayValue(_values: Expression<unknown>[]):
AliasableExpression<number> in the MySQL dialect and change the throw new
Error('MySQL does not support array value') to throw
createNotSupportedError('array values') (or the project's existing not-supported
message) so it matches other not-supported array helpers and centralizes error
handling.

In `@packages/orm/src/client/crud/dialects/sqlite.ts`:
- Around line 450-452: Replace the generic thrown Error in the override
buildArrayValue method with a call to createNotSupportedError so the "array
values not supported" case uses the same not-supported error type as other array
helpers; locate the buildArrayValue method and change the throw new
Error('SQLite does not support array values') to return or throw (matching
surrounding helpers) createNotSupportedError('array values') or the same message
used elsewhere to preserve consistent error typing.

In `@packages/orm/src/client/executor/name-mapper.ts`:
- Around line 332-367: In transformCast (name-mapper.ts) replace the
unconditional invariant(parameters.length === 1) with a guard that only enters
the enum-mapping branch when node.dataType.parameters.length === 1 and the other
checks (RawNode.is, ReferenceNode.is, ColumnNode.is) succeed; i.e., wrap the
existing enum-detection/mapping logic in an if that checks parameters.length ===
1 (and keep existing ReferenceNode/ColumnNode checks) so RAW types like
varchar(255) or decimal(10,2) don't throw, while preserving behavior of
mapEnumTypeName, getEnum and subsequent ValueNode / CastNode handling.

In `@packages/sdk/src/ts-schema-generator.ts`:
- Line 38: Remove the unused type import "ResolvedType" from the import list in
ts-schema-generator.ts; find the import statement that includes "type
ResolvedType" and delete that symbol so the file no longer imports ResolvedType
(leave other imported symbols intact).

Comment thread packages/orm/src/client/crud/dialects/mysql.ts Outdated
Comment thread packages/orm/src/client/crud/dialects/sqlite.ts Outdated
Comment thread packages/orm/src/client/executor/name-mapper.ts Outdated
Comment thread packages/sdk/src/ts-schema-generator.ts Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jan 28, 2026

Claude finished @ymc9's task —— View job


Claude Code Review Complete


Code Review Feedback

Understanding the Issue

The issue was that literal array values in policy expressions (like hasSome(auth().roles, [admin, editor])) were not properly handled for PostgreSQL. The transformer was creating ValueListNode instead of using dialect-specific array value generation, causing PostgreSQL array syntax errors.

✅ Positive Aspects

  1. Clear Problem Identification: The regression test clearly demonstrates the issue with a real-world scenario involving enum arrays in auth policies.

  2. Proper Abstraction: The fix correctly delegates array value creation to dialect-specific implementations via buildArrayValue().

  3. Comprehensive Dialect Support: All three dialects are properly updated:

    • PostgreSQL: Uses ARRAY[...] syntax (supports arrays)
    • MySQL/SQLite: Properly throw errors since they don't support native arrays
  4. Type Safety: Added proper return type annotation to transformValue() method.

🔍 Code Quality Issues

  1. packages/plugins/policy/src/expression-transformer.ts:184-186 - Good change to use dialect-specific array building, but consider caching the expression builder instance.

  2. packages/plugins/policy/src/expression-transformer.ts:558-562 - The nested transformValue call for array elements should preserve element types rather than passing the parent array type.

🐛 Potential Bug

In transformValue() at lines 558-562:

return this.dialect
    .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, type))))
    .toOperationNode();

Issue: Passing the array's type to nested elements is incorrect. For enum arrays like Role[], each element should be typed as Role, not Role[].

Fix: Consider element type detection:

// Get element type for arrays
const elementType = type.endsWith('[]') ? type.slice(0, -2) : type;
return this.dialect
    .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, elementType))))
    .toOperationNode();

⚡ Performance Considerations

  1. Expression Builder Instance: Creating new ExpressionWrapper() for each array element could be optimized.

  2. Recursive Type Checking: The QueryUtils.isEnum() check in transformValue() is called for every value - consider memoization for frequently used types.

🔒 Security Assessment

  • No security concerns identified
  • Proper input validation and type checking maintained
  • SQL injection prevention through parameterized queries preserved

🧪 Test Coverage

  • Excellent: Comprehensive regression test covering the specific issue
  • Good: Tests include enum array handling with @map attribute
  • Coverage: Multiple auth scenarios (admin, editor, viewer roles)

📋 Minor Improvements

  1. packages/orm/src/client/crud/dialects/postgresql.ts: The removal of enum array casting logic appears correct, but ensure this doesn't break other PostgreSQL enum array scenarios.

  2. Type Definitions: Consider adding JSDoc to buildArrayValue() method explaining when it should throw vs. when it should work.

🎯 Recommendations

  1. Fix the type propagation bug in transformValue() for array elements
  2. Add integration tests for nested enum arrays to ensure the fix works comprehensively
  3. Consider performance optimization for expression wrapper creation
  4. Document the dialect differences in array handling for future maintainers

Overall Assessment: ✅ APPROVE with Minor Fixes

This is a solid fix that properly addresses the PostgreSQL array handling issue. The core architecture change (delegating to dialect-specific array building) is correct and well-implemented. The main concern is the type propagation bug for array elements, which should be addressed before merging.

The test coverage is excellent and the changes maintain backward compatibility. Once the type issue is fixed, this PR will be ready to merge.


Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/orm/src/client/executor/name-mapper.ts (1)

268-271: Missing queryId argument in early return.

Line 270 calls super.transformUpdateQuery(node) without passing queryId, while line 265 correctly passes it. This breaks the per-query context threading for update queries with non-table nodes.

Proposed fix
     const { alias, node: innerTable } = stripAlias(node.table);
     if (!innerTable || !TableNode.is(innerTable)) {
-        return super.transformUpdateQuery(node);
+        return super.transformUpdateQuery(node, queryId);
     }
🧹 Nitpick comments (1)
packages/orm/src/client/executor/name-mapper.ts (1)

343-344: Remove unnecessary non-null assertion.

The ! assertion on getEnum(...) is misleading since the very next line checks if (enumDef). Removing it makes the code intent clearer.

Suggested change
-            const enumDef = getEnum(this.schema, castTypeName)!;
-            if (enumDef) {
+            const enumDef = getEnum(this.schema, castTypeName);
+            if (enumDef) {

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jan 28, 2026

Claude finished @ymc9's task —— View job


Claude Code Review Complete


Review Todo List:

  • Read the regression test to understand the specific issue being fixed
  • Review core changes in the policy expression evaluation logic
  • Analyze dialect-specific implementations (PostgreSQL, MySQL, SQLite)
  • Check ORM client changes and name mapping logic
  • Evaluate code quality, potential bugs, and security concerns
  • Assess performance implications
  • Review test coverage adequacy
  • Provide comprehensive feedback and recommendations

Code Review Feedback

✅ Issue Understanding

This PR successfully fixes PostgreSQL array literal handling in policy evaluation. The issue was that enum arrays in expressions like hasSome(auth().roles, [admin, editor]) were creating malformed SQL due to improper array value generation for PostgreSQL's ARRAY[...] syntax.

✅ Architecture & Design Quality

Excellent Design Decisions:

  1. New EnumExpression Type - Clean separation of enum values from string literals in the AST
  2. Dialect Abstraction - Proper delegation to buildArrayValue() method allows database-specific implementations
  3. Comprehensive Mapping - Enhanced name mapping for enum types and values in CAST operations
  4. Expression System Consistency - All expression types handled uniformly through the visitor pattern

🔍 Code Quality Assessment

Strengths:

  • Clear Separation of Concerns: Each dialect properly implements its array capabilities
  • Type Safety: New enum expression type provides better type checking
  • Consistent Error Handling: MySQL/SQLite properly throw errors for unsupported arrays
  • Comprehensive Mapping: Full enum type and value name mapping support

Minor Issues Identified:

  1. packages/plugins/policy/src/expression-transformer.ts:560 - Type Propagation Bug

    // Current (incorrect):
    return this.dialect
        .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, type))))
        .toOperationNode();
    
    // Should be (fix element type):
    const elementType = type.endsWith('[]') ? type.slice(0, -2) : type;
    return this.dialect
        .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, elementType))))
        .toOperationNode();

    Issue: Passing array type to elements instead of element type (e.g., Role[] instead of Role)

  2. packages/plugins/policy/src/expression-transformer.ts:632-656 - Dead Code
    Large block of commented code should be removed to improve maintainability.

⚡ Performance Implications

Positive:

  • Dialect-specific implementations avoid unnecessary abstraction overhead
  • Direct SQL generation for PostgreSQL arrays is efficient

Minor Concerns:

  • Creating ExpressionWrapper instances for each array element could be optimized
  • QueryUtils.isEnum() calls for every value - consider memoization for frequently used types

🔒 Security Assessment

No security concerns identified

  • Proper parameterization maintained
  • No SQL injection vectors introduced
  • Input validation preserved through existing mechanisms

🧪 Test Coverage

Excellent test coverage

  • Comprehensive regression test covering the exact reported issue
  • Tests include enum arrays with @map attributes
  • Multiple auth role scenarios (admin, editor, viewer)
  • Real-world policy expressions tested

💡 Recommendations

Must Fix:

  1. Fix type propagation bug in transformValue() method for array elements

Should Consider:

  1. Remove commented code in expression transformer
  2. Add JSDoc to buildArrayValue() explaining dialect differences
  3. Performance optimization for expression wrapper creation in arrays

Nice to Have:

  1. Integration tests for nested enum arrays
  2. Documentation of dialect array support differences

📋 Dialect Implementation Review

  • PostgreSQL: ✅ Proper ARRAY[...] syntax implementation
  • MySQL: ✅ Correctly throws error (no native array support)
  • SQLite: ✅ Correctly throws error (no native array support)

🎯 Overall Assessment: ✅ APPROVE with Minor Fix

This is a solid, well-architected fix that properly addresses the PostgreSQL array handling issue. The core design of delegating to dialect-specific array building is excellent and maintains the abstraction boundaries properly.

The main concern is the type propagation bug for array elements, which should be addressed before merging. Once fixed, this PR significantly improves PostgreSQL enum array handling while maintaining compatibility across all supported databases.

Impact: This fix enables proper policy evaluation with enum arrays on PostgreSQL, resolving a critical functional gap in the ORM's authorization system.


@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Jan 28, 2026

Claude finished @ymc9's task —— View job


Claude Code Review Complete


Review Todo List:

  • Read the regression test to understand the specific issue being fixed
  • Review core changes in the policy expression evaluation logic
  • Analyze dialect-specific implementations (PostgreSQL, MySQL, SQLite)
  • Check ORM client changes and name mapping logic
  • Evaluate code quality, potential bugs, and security concerns
  • Assess performance implications
  • Review test coverage adequacy
  • Provide comprehensive feedback and recommendations

Code Review Feedback

✅ Issue Understanding

This PR successfully fixes PostgreSQL array literal handling in policy evaluation. The issue was that enum arrays in expressions like hasSome(auth().roles, [admin, editor]) were creating malformed SQL due to improper array value generation for PostgreSQL's ARRAY[...] syntax.

✅ Architecture & Design Quality

Excellent Design Decisions:

  1. Proper Dialect Abstraction - The solution correctly delegates array value creation to dialect-specific implementations via buildArrayValue()
  2. Comprehensive Array Operations - Added full support for buildArrayContains, buildArrayHasEvery, and buildArrayHasSome across all dialects
  3. Clean Expression Transformation - The transformValue() method now properly handles arrays by delegating to the dialect
  4. Consistent Error Handling - MySQL/SQLite properly throw errors for unsupported array operations

🔍 Code Quality Assessment

Strengths:

  • Clear Separation of Concerns: Each dialect properly implements its array capabilities
  • Consistent API: All dialects implement the same interface with appropriate error handling
  • Proper PostgreSQL Implementation: Uses correct ARRAY[...] syntax with type casting
  • Function Delegation: The has, hasSome, hasEvery functions correctly delegate to dialect methods

Minor Issues Identified:

  1. packages/plugins/policy/src/expression-transformer.ts:558 - Type Propagation Issue

    // Current (incorrect):
    return this.dialect
        .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, type))))
        .toOperationNode();
    
    // Should be (fix element type):
    const elementType = type.endsWith('[]') ? type.slice(0, -2) : type;
    return this.dialect
        .buildArrayValue(value.map((v) => new ExpressionWrapper(this.transformValue(v, elementType))))
        .toOperationNode();

    Issue: Passing array type to elements instead of element type (e.g., Role[] instead of Role)

  2. packages/plugins/policy/src/expression-transformer.ts:166-184 - TypeScript Suppressions
    The @ts-expect-error comments should be replaced with proper type annotations for better maintainability.

⚡ Performance Implications

Positive:

  • Dialect-specific implementations avoid unnecessary abstraction overhead
  • Direct SQL generation for PostgreSQL arrays is efficient
  • No significant performance regressions expected

Minor Concerns:

  • Creating ExpressionWrapper instances for each array element could be optimized
  • Consider memoization for frequently used enum type checks

🔒 Security Assessment

No security concerns identified

  • Proper parameterization maintained for all SQL generation
  • No SQL injection vectors introduced
  • Input validation preserved through existing mechanisms

🧪 Test Coverage

Excellent test coverage

  • Comprehensive regression test covering the exact reported issue
  • Tests include enum arrays with @map attributes
  • Multiple auth role scenarios (admin, editor, viewer)
  • Real-world policy expressions with has() and hasSome() functions

💡 Recommendations

Must Fix:

  1. Fix type propagation bug in transformValue() method for array elements (see code suggestion above)

Should Consider:

  1. Replace @ts-expect-error with proper type annotations in expression handlers
  2. Add JSDoc to new dialect methods explaining when they throw vs. when they work
  3. Performance optimization for expression wrapper creation in arrays

Nice to Have:

  1. Integration tests for complex nested enum arrays
  2. Documentation of dialect array support differences for maintainers

📋 Dialect Implementation Review

  • PostgreSQL: ✅ Excellent implementation with proper ARRAY[...] syntax and type casting
  • MySQL: ✅ Correctly throws errors (no native array support)
  • SQLite: ✅ Correctly throws errors (no native array support)

🎯 Overall Assessment: ✅ APPROVE with Minor Fix

This is a solid, well-architected fix that properly addresses the PostgreSQL array handling issue. The core design of delegating to dialect-specific array building is excellent and maintains the abstraction boundaries properly.

The main concern is the type propagation bug for array elements, which should be addressed before merging. Once fixed, this PR significantly improves PostgreSQL enum array handling while maintaining compatibility across all supported databases.

Impact: This fix enables proper policy evaluation with enum arrays on PostgreSQL, resolving a critical functional gap in the ORM's authorization system.


Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/orm/src/client/executor/name-mapper.ts (1)

261-269: Propagate queryId in the non-TableNode update path.

Line 268 drops queryId when returning from the non-TableNode branch, inconsistent with all other early returns in this method (lines 263, 275) and the pattern across other transform overrides (transformSelectQuery, transformInsertQuery, transformDeleteQuery, etc.). This loss of context can break per-query mapping.

🛠️ Suggested fix
-            return super.transformUpdateQuery(node);
+            return super.transformUpdateQuery(node, queryId);
packages/plugins/policy/src/expression-transformer.ts (1)

550-569: MySQL dialect returns Expression objects from transformInput that require special handling.

MySQL's transformInput method returns Expression objects (via this.eb.cast()) when handling JsonNullClass instances and JSON type transformations (lines 72 and 112). The current code at lines 564-569 treats these as simple primitives and wraps them with ValueNode.createImmediate(), which serializes the object instead of converting it to an operation node.

Detect expression objects by checking for the toOperationNode method and return their operation node directly:

🛠️ Suggested fix
-            if (typeof transformed !== 'string') {
-                // simple non-string primitives can be immediate values
-                return ValueNode.createImmediate(transformed);
-            } else {
-                return ValueNode.create(transformed);
-            }
+            if (typeof transformed === 'string') {
+                return ValueNode.create(transformed);
+            }
+            if (transformed && typeof transformed === 'object' && 'toOperationNode' in transformed) {
+                return (transformed as Expression<unknown>).toOperationNode();
+            }
+            // simple non-string primitives can be immediate values
+            return ValueNode.createImmediate(transformed);

@ymc9 ymc9 merged commit 209312a into dev Jan 28, 2026
13 of 22 checks passed
@ymc9 ymc9 deleted the fix/issue-595 branch January 28, 2026 16:15
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Array-related functions (has, hasSome) in access policies fail on PostgreSQL

2 participants